﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lilium.Collections;

namespace Lilium.Web.Mvc.Security
{
    /// <summary>
    /// Base class for AuthorizationService implementation.
    /// </summary>
    public abstract class ActionAuthorizationService : IAuthorizationService
    {
        public ActionAuthorizationService(AuthorizationStrategy authorizationStrategy, DefaultAuthorizationAction defaultAction)
        {
            m_AuthorizationStrategy = authorizationStrategy;
            m_DefaultAction = defaultAction;
        }

        #region IAuthorizationService Members

        public bool Authorize(AuthorizationContext authorizationContext)
        {
            if (authorizationContext == null) throw new ArgumentNullException("authorizationContext");

            BeforeAuthorize(authorizationContext);

            var authorizationResult = ActionAuthorizationResult.Inherit; // выставляем наследование, на случай, если операция не содержит токенов.

            using (var tokenArgs = GetTokens(authorizationContext).Select(token => new ActionAuthorizationEventArgs(
                        authorizationContext, 
                        authorizationContext.Area, 
                        authorizationContext.Controller, 
                        authorizationContext.Action, 
                        token)).GetEnumerator())

                if (tokenArgs.MoveNext())
                {
                    authorizationResult = ActionAuthorizationResult.Undefined; // отключаем наследование, теперь это должно контролироваться токенами
                    var scanNext = true;
                    do
                    {
                        OnAuthorize(tokenArgs.Current);
                        authorizationContext.AuthorizationMessages.AddRange(tokenArgs.Current.AuthorizationMessages);
                        switch (tokenArgs.Current.Result)
                        {
                            case ActionAuthorizationResult.Allow: 
                            // если используется разрешительная стратерия, первое разрешение означает, что доступ получен
                                authorizationResult = ActionAuthorizationResult.Allow;
                                if (AuthorizationStrategy == AuthorizationStrategy.Permissive)
                                    scanNext = false;
                                break;
                            case ActionAuthorizationResult.Deny: 
                            // если используется запретительная стратерия, первый запрет приводит к полному запрету действия
                                authorizationResult = ActionAuthorizationResult.Deny;
                                if (AuthorizationStrategy == AuthorizationStrategy.Prohibitive)
                                    scanNext = false;
                                break;
                            case ActionAuthorizationResult.Inherit:
                                // если есть токен с наследованием, включается наследование.
                                if (authorizationResult == ActionAuthorizationResult.Undefined)
                                    authorizationResult = ActionAuthorizationResult.Inherit;
                                break;
                        }
                    } while (scanNext && tokenArgs.MoveNext());
                }

            if (authorizationResult == ActionAuthorizationResult.Inherit)
            {
                var tokenArgs = new ActionAuthorizationEventArgs(
                        authorizationContext, 
                        authorizationContext.Area, 
                        authorizationContext.Controller, 
                        authorizationContext.Action, 
                        null);
                OnAuthorize(tokenArgs);
                authorizationContext.AuthorizationMessages.AddRange(tokenArgs.AuthorizationMessages);
                if ((authorizationResult = tokenArgs.Result) == ActionAuthorizationResult.Inherit)
                {
                    tokenArgs = new ActionAuthorizationEventArgs(
                            authorizationContext, 
                            authorizationContext.Area, 
                            authorizationContext.Controller, 
                            null, 
                            null);
                    OnAuthorize(tokenArgs);
                    authorizationContext.AuthorizationMessages.AddRange(tokenArgs.AuthorizationMessages);
                    if ((authorizationResult = tokenArgs.Result) == ActionAuthorizationResult.Inherit)
                    {
                        tokenArgs = new ActionAuthorizationEventArgs(
                                authorizationContext, 
                                authorizationContext.Area, 
                                null, 
                                null, 
                                null);
                        OnAuthorize(tokenArgs);
                        authorizationContext.AuthorizationMessages.AddRange(tokenArgs.AuthorizationMessages);
                        authorizationResult = tokenArgs.Result;
                    }
                }
            }

            bool authorized =
                   (authorizationResult == ActionAuthorizationResult.Allow)
                || (authorizationResult != ActionAuthorizationResult.Deny && DefaultAction == DefaultAuthorizationAction.Allow);

            if (authorized)
                OnAllow(authorizationContext);
            else
                OnDeny(authorizationContext);

            return authorized;
        }

        #endregion

        protected virtual void BeforeAuthorize(AuthorizationContext context)
        {
        }

        protected virtual void OnAllow(AuthorizationContext context)
        {
        }

        protected abstract void OnAuthorize(ActionAuthorizationEventArgs args);

        protected virtual void OnDeny(AuthorizationContext context)
        {
        }

        protected virtual IEnumerable<string> GetTokens(AuthorizationContext authorizationContext)
        {
            yield break;
        }

        #region private AuthorizationStrategy AuthorizationStrategy

        private readonly AuthorizationStrategy m_AuthorizationStrategy;

        protected AuthorizationStrategy AuthorizationStrategy
        {
            get
            {
                return m_AuthorizationStrategy;
            }
        }

        #endregion

        #region public DefaultAuthorizationAction DefaultAction

        private readonly DefaultAuthorizationAction m_DefaultAction;

        protected DefaultAuthorizationAction DefaultAction
        {
            get
            {
                return m_DefaultAction;
            }
        }

        #endregion
    }
}